/*
 * Copyright 2020-present Open Networking Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.onosproject.net.behaviour.inbandtelemetry;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.annotations.Beta;
import com.google.common.collect.Sets;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.config.Config;
import org.onosproject.ui.JsonUtils;

import java.util.Collections;
import java.util.Set;

/**
 * Application level configuration of the INT process.
 * Config example:
 * {
 *   "apps": {
 *     "org.onosproject.inbandtelemetry": {
 *       "report": {
 *         "collectorIp": "192.168.0.1",
 *         "collectorPort": 5500,
 *         "minFlowHopLatencyChangeNs": 300,
 *         "watchSubnets": [
 *           "192.168.0.0/24",
 *           "10.140.0.0/16"
 *         ]
 *       }
 *     }
 *   }
 * }
 */
@Beta
public final class IntReportConfig extends Config<ApplicationId> {
    private static final String COLLECTOR_IP = "collectorIp";
    private static final String COLLECTOR_PORT = "collectorPort";
    private static final String MIN_FLOW_HOP_LATENCY_CHANGE_NS = "minFlowHopLatencyChangeNs";
    private static final String COLLECTOR_NEXT_HOP_MAC = "collectorNextHopMac";
    private static final String SINK_IP = "sinkIp";
    private static final String SINK_MAC = "sinkMac";
    private static final String WATCH_SUBNETS = "watchSubnets";

    /**
     * IP address of the collector.
     * This is the destination IP address that will be used for all INT reports
     * generated by all INT devices.
     *
     * @return collector IP address, null if not present
     */
    public IpAddress collectorIp() {
        if (object.hasNonNull(COLLECTOR_IP)) {
            return IpAddress.valueOf(JsonUtils.string(object, COLLECTOR_IP));
        } else {
            return null;
        }
    }

    /**
     * UDP port number of the collector.
     * This is the destination UDP port number that will be used for all INT reports
     * generated by all INT devices.
     *
     * @return collector UDP port number, null if not present
     */
    public TpPort collectorPort() {
        if (object.hasNonNull(COLLECTOR_PORT)) {
            return TpPort.tpPort((int) JsonUtils.number(object, COLLECTOR_PORT));
        } else {
            return null;
        }
    }

    /**
     * Gets the minimal interval of hop latency change for a flow.
     * This value is used to instruct an INT-capable device to produce reports
     * only for packets which hop latency changed by at least minFlowHopLatencyChangeNs
     * from the previously reported value for the same flow (5-tuple), i.e., produce a
     * report only if `(currentHopLatency - previousHopLatency) &gt; minFlowHopLatencyChangeNs`.
     * Some device implementations might support only specific intervals, e.g., powers of 2.
     *
     * @return Interval in nanoseconds
     */
    public int minFlowHopLatencyChangeNs() {
        if (object.hasNonNull(MIN_FLOW_HOP_LATENCY_CHANGE_NS)) {
            return (int) JsonUtils.number(object, MIN_FLOW_HOP_LATENCY_CHANGE_NS);
        } else {
            return 0;
        }
    }

    /**
     * Returns MAC address of next hop of INT report packets.
     * This can be either MAC address of the collector or a router.
     * This is an optional parameter, which means that the usage of this
     * parameter depends on IntProgrammable implementation.
     * (e.g., If a report packet needs to be routed to reach the collector,
     * IntProgrammable will ignore this value and choose next hop router's MAC address.
     * If a collector itself is the next hop of INT report packets, then
     * this value will be used as a destination MAC address for all INT report packets.)
     *
     * @return MAC address of next hop of INT report packets, null if not present
     */
    public MacAddress collectorNextHopMac() {
        if (object.hasNonNull(COLLECTOR_NEXT_HOP_MAC)) {
            return MacAddress.valueOf(JsonUtils.string(object, COLLECTOR_NEXT_HOP_MAC));
        } else {
            return null;
        }
    }

    /**
     * Returns IP address of the sink device.
     * All sink devices share this address as the source IP address
     * for all INT reports.
     *
     * @return sink device's IP address, null if not present
     */
    public IpAddress sinkIp() {
        if (object.hasNonNull(SINK_IP)) {
            return IpAddress.valueOf(JsonUtils.string(object, SINK_IP));
        } else {
            return null;
        }
    }

    /**
     * Returns MAC address of the sink device.
     * All sink devices share this address as the source MAC address
     * for all INT reports.
     *
     * @return sink device's MAC address, null if not present
     */
    public MacAddress sinkMac() {
        if (object.hasNonNull(SINK_MAC)) {
            return MacAddress.valueOf(JsonUtils.string(object, SINK_MAC));
        } else {
            return null;
        }
    }

    /**
     * Gets subnets to be watched.
     *
     * @return subnets to be watched
     */
    public Set<IpPrefix> watchSubnets() {
        if (object.hasNonNull(WATCH_SUBNETS) && object.path(WATCH_SUBNETS).isArray()) {
            Set<IpPrefix> subnets = Sets.newHashSet();
            ArrayNode subnetArray = (ArrayNode) object.path(WATCH_SUBNETS);
            subnetArray.forEach(subnetNode -> {
                subnets.add(IpPrefix.valueOf(subnetNode.asText()));
            });
            return subnets;
        } else {
            return Collections.EMPTY_SET;
        }
    }

    /**
     * Sets the collector IP to the config.
     *
     * @param collectorIp the collector IP
     * @return the config
     */
    public IntReportConfig setCollectorIp(IpAddress collectorIp) {
        return (IntReportConfig) setOrClear(COLLECTOR_IP, collectorIp.toString());
    }

    /**
     * Sets the collector port to the config.
     *
     * @param port the collector port
     * @return the config
     */
    public IntReportConfig setCollectorPort(TpPort port) {
        return (IntReportConfig) setOrClear(COLLECTOR_PORT, port.toInt());
    }

    /**
     * Sets the minimal interval of hop latency change
     * for a flow to the config.
     *
     * @param ns the value of hop latency change
     * @return the config
     */
    public IntReportConfig setMinFlowHopLatencyChangeNs(int ns) {
        return (IntReportConfig) setOrClear(MIN_FLOW_HOP_LATENCY_CHANGE_NS, ns);
    }

    /**
     * Sets collector next hop mac address to the config.
     *
     * @param collectorNextHopMac the collector next hop mac address
     * @return the config
     */
    public IntReportConfig setCollectorNextHopMac(MacAddress collectorNextHopMac) {
        return (IntReportConfig) setOrClear(COLLECTOR_NEXT_HOP_MAC, collectorNextHopMac.toString());
    }

    /**
     * Sets the sink IP address to the config.
     *
     * @param sinkIp the sink IP address
     * @return the config
     */
    public IntReportConfig setSinkIp(IpAddress sinkIp) {
        return (IntReportConfig) setOrClear(SINK_IP, sinkIp.toString());
    }

    /**
     * Sets the sink mac address to the config.
     *
     * @param sinkMac the sink mac address
     * @return the config
     */
    public IntReportConfig setSinkMac(MacAddress sinkMac) {
        return (IntReportConfig) setOrClear(SINK_MAC, sinkMac.toString());
    }

    /**
     * Sets subnets to be watched.
     *
     * @param subnets subnets to be watched.
     * @return the config
     */
    public IntReportConfig setWatchSubnets(Set<IpPrefix> subnets) {
        return (IntReportConfig) setOrClear(WATCH_SUBNETS, subnets);
    }
}
